<?php
/**
* Redis
*/
class IRedis extends Redis
{
    public static $instance;

    public static $traceCode = '';
    
    /**
     * 确保每次处理PHP请求时, 每个reids host只有一个连接
     * @param string $hostName RedisConfig 中的配置项, 以host为单元分组
     * @return mixed
     */
	public static function getInstance($hostName='localhost')
    {
        //var_dump(IRedis::$instance);
        if (empty(IRedis::$instance[$hostName])) {
            self::$instance[$hostName] = new IRedis($hostName);
        }
        return self::$instance[$hostName];
    }
    
    /**
     * 因为Redis的构造函数是public所以这里也是public的
     * IRedis constructor.
     * @param $hostName
     */
	public function __construct($hostName)
	{
		parent::__construct();
        list($host, $port, $auth) = RedisConfig::$hosts[$hostName];
        try {
            $this->connect($host, $port);
            //$this->pconnect($host, $port);
            !empty($auth) && $this->auth($auth);
        } catch (Exception $e) {
            throw new Exception('Redis 链接错误');
        }
	}

    /**
     * desc 获取存储在redis中的键名, 补上前缀和后缀
     * 所有的键名都写在同一个地方(例如 config/RedisConfig.php)方便键名统一管理
     * @param string|array  $key 键名后缀(以数组形式传递, 最终转化为以下划线形式链接)
     * @throws Exception
     * @return string
     */
    public static function getFullKeyName($key)
    {
        //键名不能为空
        if (empty($key)) {
            throw new Exception('Redis键名传入为空');
        }

        //变量后缀用下划线链接
        //字符串与变量用冒号隔开, 管理软件默认分割符
        if (is_array($key)) {
            $key = implode(':', $key);
        }

        return $key;
    }

    /**
     * 往队列塞消息/不存在就创建 (样例)
     * @param string $key
     * @param string|json|array $value
     * @return int
     */
    public static function pushQueue($key, $value)
    {
        if (is_array($value)) {
            $value = json_encode($value);
        }
        return IRedis::getInstance()->lPush($key, $value);
    }

    //删除队列
    public static function delQueue($key)
    {
        return IRedis::getInstance()->del($key);
    }

    /**
     * 消费队列
     * 用法参考: https://github.com/phpredis/phpredis/#blpop-brpop
     * @param array|string $keys 队列名
     * @param int $holdTime 阻塞时间
     */
    public static function blockPopQueue($keys, $holdTime = 0)
    {
        while(true) {
            list($keyName, $jsonValue) = IRedis::getInstance()->brPop($keys, $holdTime);
            if (!empty($jsonValue)) {
                self::callBack($keyName, $jsonValue);
            }
        }
    }

    /**
     * 退出pop进程
     * @param string $key 名字不能为空
     * @return bool
     */
    public static function killPopQueue($key)
    {
        if (empty($key)) {
            return false;
        }
        $key = escapeshellcmd($key);
        $command = "ps -ef | grep php | grep blockpop | grep $key | awk '{print $2}' | xargs kill ";
        exec($command, $output);
        print_r($output);
    }

    /**
     * 重启pop进程
     */
    public static function restartQueue($key)
    {
        self::killPopQueue($key);
        self::watchPopQueue($key);
        return true;
    }

    /**
     * 批量退出pop进程
     * @param array $arrKeys
     * @return bool
     */
    public static function mKillPopQueue($arrKeys = array())
    {
        if (empty($key)) {
            return false;
        }

        foreach ($arrKeys as $key) {
            $key = escapeshellcmd($key);
            self::killPopQueue($key);
        }
    }

    /**
     * 读取消息后交给处理程序处理
     * @param string $key 队列名
     * @param string $params 读取到的数据, 只支持json
     */
    public static function callBack($key, $params)
    {
        self::$traceCode = Fun::randChar('10');
        self::log("开始处理消息: $key $params");
        try {
            $phpVersion = substr(PHP_VERSION,0,3);
            $arr = ($phpVersion >= '7.3') ? json_decode($params, true, JSON_THROW_ON_ERROR) : json_decode($params, true);
            $rs = '';
            if (!empty($arr['_callback'])) {
                $callBackInfo = QueueConfig::getCallBack($arr['_callback']);
                $class = $callBackInfo['class']; //必须有_class
                $method = $callBackInfo['method']; //必须有_method
                $rs = call_user_func([$class, $method], $arr); //处理消息
            }
            self::log('处理结束: '.$rs);
        } catch (Exception $e) {
            self::error($e->getMessage());
        }
    }

    /**
     * 判断是否操作过于频繁
     * @param string $rk redis键名
     * @param int $expire 锁定时间, 单位:秒
     * @return bool
     */
    public static function tooQuick($rk, $expire=60)
    {
        $redis = self::getInstance();
        if (!$redis->setNx($rk, 1)) {
            return true; //重复提交
        } else {
            $redis->expire($rk, $expire);
            return false;
        }
    }

    public static function error($msg)
    {
        FileLog::ini('queue')->info(self::$traceCode.' '.$msg);
    }

    public static function log($msg)
    {
        FileLog::ini('queue')->info(self::$traceCode.' '.$msg);
    }


}